/*
 * GameStartedGameStateController.cpp
 *
 *  Created on: Dec 29, 2011
 *      Author: rudi
 */
#include <wx/socket.h>
#include "GameStartedGameStateController.h"
#include "App.h"
#include "MainFrame.h"
#include "MainPanel.h"
#include "MessagingFrame.h"
#include "MessagingPanel.h"
#include "IGameController.h"
#include "IdleGameStateController.h"
#include "ServerStartingGameStateController.h"
#include "RemoteTurnSubStateController.h"
#include "LocalTurnSubStateController.h"
#include "ClientStartingGameStateController.h"
#include "LevelDownloadingGameStateController.h"
#include "NetworkEventData.h"
#include "Configuration.h"
#include "SmartPointer.h"
#include "WxUtils.h"

// TODO: This class is still using the old way of reading data...

/**
 * GameStartedGameStateController::ChangeSubStateTask class
 */

GameStartedGameStateController::ChangeSubStateTask::ChangeSubStateTask(GameStartedGameStateController* gameStartedController,
																	   SmartPointer<GameStartedSubStateController> subStateController): mGameStartedController(gameStartedController),
																																		mSubStateController(subStateController)
{
	// Nothing to do yet
}

/*virtual*/
GameStartedGameStateController::ChangeSubStateTask::~ChangeSubStateTask()
{
	// Nothing to do yet
}

void GameStartedGameStateController::ChangeSubStateTask::execute()
{
	mGameStartedController->mSubStateController = mSubStateController;
	// Force a refresh
	wxGetApp().getMainFrame()->getMainPanel()->Refresh();
}

/**
 * GameStartedGameStateController class
 */

GameStartedGameStateController::GameStartedGameStateController(SmartPointer<GameStartedSubStateController> subStateController): mSubStateController(subStateController)
{
	mSubStateController->addGameStartedController(this);
	// Disabling and enabling menupoints according to the started game state
	wxGetApp().getMainFrame()->setRestartEnabled(true);
	wxGetApp().getMainFrame()->setMessagingEnabled(true);
	wxGetApp().getMainFrame()->setStartHostingEnabled(false);
	wxGetApp().getMainFrame()->setChangeLevelEnabled(true);
	wxGetApp().getMainFrame()->setKickOffClientEnabled(true);
}

GameStartedGameStateController::GameStartedGameStateController(bool isRestartFromUser): mSubStateController(NULL)
{
	unsigned int size = sizeof(NetworkEventData);

	if(NULL == wxGetApp().getServerSocket())
		mSubStateController = new RemoteTurnSubStateController();
	else
		mSubStateController = new LocalTurnSubStateController();
	mSubStateController->addGameStartedController(this);
	if(true == isRestartFromUser)
	{
		// We need to send the restart to the remote side
		SmartPointer<NetworkEventData> networkEventData = new NetworkEventData();

		networkEventData->setDataType(NetworkEventData::RESTART);
		wxGetApp().getClientSocket()->Write(networkEventData, size);
	}
	// Disabling and enabling menupoints according to the started game state
	wxGetApp().getMainFrame()->setRestartEnabled(true);
	wxGetApp().getMainFrame()->setMessagingEnabled(true);
	wxGetApp().getMainFrame()->setStartHostingEnabled(false);
	wxGetApp().getMainFrame()->setChangeLevelEnabled(true);
	wxGetApp().getMainFrame()->setKickOffClientEnabled(true);
}

/*virtual*/
GameStartedGameStateController::~GameStartedGameStateController()
{
	// Nothing to do yet
}

//
// Methods inherited from Controller
//

void GameStartedGameStateController::handleMouseDown(const wxPoint& position)
{
	mSubStateController->handleMouseDown(position);
}

void GameStartedGameStateController::handleMouseUp(const wxPoint& position)
{
	mSubStateController->handleMouseUp(position);
}

bool GameStartedGameStateController::handleMouseMotion(const wxPoint& position)
{
	return mSubStateController->handleMouseMotion(position);
}

const wxBitmap& GameStartedGameStateController::getBitmapToPaint() const
{
	return mSubStateController->getBitmapToPaint();
}

wxBitmap& GameStartedGameStateController::getBitmapToDraw()
{
	return mSubStateController->getBitmapToDraw();
}

//
// Methods inherited from GameStateController
//

void GameStartedGameStateController::handleSocketConnection(wxSocketEvent& /*socketEvent*/)
{
	wxSocketServer* serverSocket = static_cast<wxSocketServer*>(wxGetApp().getServerSocket());
	SmartPointer<NetworkEventData> data = new NetworkEventData();

	wxASSERT_MSG(serverSocket != NULL, wxT("GameStartedGameStateController::handleSocketConnection called on client side. Could not happen!"));
	data->rejectConnection(serverSocket);
}

void GameStartedGameStateController::handleSocketLost(wxSocketEvent& socketEvent)
{
	wxSocketBase* socket = wxGetApp().getClientSocket();
	wxSocketBase* serverSocket = wxGetApp().getServerSocket();
	IGameController* gameController = wxGetApp().getMainFrame()->getMainPanel()->getGameController();
	MainFrame::DialogMessageType dialogMessageType = MainFrame::INVALID;
	IGameStateController* gameStateController = NULL;

	// If the disconnection is on an other socket, there is nothing to do (very unlikely)
	if(socketEvent.GetSocket() != socket)
		return;
	// The remote side has been disconnected
	socket->Destroy();
	socket = NULL;
	wxGetApp().setClientSocket(NULL);
	if(NULL == serverSocket)
	{
		dialogMessageType = MainFrame::CLIENT_ERROR;
		// If we are the client part, we need to revert back to Idle state
		gameStateController = new IdleGameStateController();
	}
	else
	{
		dialogMessageType = MainFrame::SERVER_ERROR;
		// If we are the server part we should continue for waiting for incoming connections
		gameStateController = new ServerStartingGameStateController(wxGetApp().getActualLevel());
	}
	wxGetApp().getMainFrame()->showMessageDialog(wxT("Connection lost!"), dialogMessageType);
	gameController->changeState(gameStateController);
}

void GameStartedGameStateController::handleSocketInput(wxSocketEvent& socketEvent)
{
	const unsigned int size = sizeof(NetworkEventData);
	ArraySmartPointer<char> buffer = NULL;
	SmartPointer<NetworkEventData> data = NULL;
	wxSocketBase* socket = wxGetApp().getClientSocket();
	wxString message(wxT("[Opponent's message]: "));
	WxUtils::Color color = WxUtils::GREEN;
	// TODO: In error cases this pointer can cause memory leakage
	ClientStartingGameStateController* clientStartingHandler = NULL;
	IGameController* gameController = wxGetApp().getMainFrame()->getMainPanel()->getGameController();

	// If this event is not happened on our socket, there is nothing to do.
	if(socketEvent.GetSocket() != socket)
		return;
	buffer = new char[size];
	// Just peek the data, in case we can not handle it...
	socket->Peek(buffer, size);
	// Check if an error occurred or not
	// TODO: Error handling?
	if(WxUtils::NONE != WxUtils::getSocketError(socket, size))
		return;
	data = new NetworkEventData(buffer);
	switch(data->getDataType())
	{
		case NetworkEventData::RESTART:
			// Read the data from the queue of the socket
			socket->Read(buffer, size);
			wxGetApp().getMainFrame()->getMainPanel()->getGameController()->finish(IGameController::RESTART_FROM_REMOTE_SIDE);
			break;
		case NetworkEventData::KICK_OFF:
			// We have been kicked off from the server. Inform the user before the connection is closed
			// The server will close the connection
			wxASSERT_MSG(NULL == wxGetApp().getServerSocket(), wxT("Kick off message received on server side!"));
			wxGetApp().getMainFrame()->showMessageDialog(wxT("The server kicked off you"), MainFrame::CLIENT_ERROR);
			break;
		case NetworkEventData::NEW_LEVEL:
			wxASSERT_MSG(NULL == wxGetApp().getServerSocket(), wxT("Invalid message received on server side"));
			// Read the data from the queue of the socket
			socket->Read(buffer, size);
			buffer = new char[data->getDataLength() + 1];
			memset(buffer, 0, data->getDataLength() + 1);
			socket->Read(buffer, data->getDataLength());
			// Check if an error occurred or not
			if(WxUtils::NONE != WxUtils::getSocketError(socket, data->getDataLength()))
			{
				socket->Destroy();
				socket = NULL;
				wxGetApp().setClientSocket(NULL);
				wxGetApp().getMainFrame()->showMessageDialog(wxT("Could not receive new level from the server!"), MainFrame::CLIENT_ERROR);
				gameController->changeState(new IdleGameStateController());

				return;
			}
			data->setRawData(buffer);
			wxGetApp().setActualLevel(data->getStringData());
			// The ClientStarting object will handle the configuration negotiation, not the connection itself
			// So there is no need to provide it with real host and port number
			clientStartingHandler = new ClientStartingGameStateController(wxT(""), 0);
			// Check if the level is known at our side
			if(NULL != wxGetApp().getConfiguration()->getLevels()->getLevel(wxGetApp().getActualLevel()))
			{
				// Send the acknowledge and change for ClientStarting state to handle configuration negotiation
				data = new NetworkEventData();
				data->setDataType(NetworkEventData::NEW_LEVEL_ACK);
				if(false == data->write(socket))
				{
					socket->Destroy();
					socket = NULL;
					wxGetApp().setClientSocket(NULL);
					wxGetApp().getMainFrame()->showMessageDialog(wxT("Could not send acknowledge to the server!"), MainFrame::CLIENT_ERROR);
					gameController->changeState(new IdleGameStateController());

					return;
				}
				gameController->changeState(clientStartingHandler);
			}
			else
			{
				// If we are allowed to download unknown levels, lets start it
				if(true ==  wxGetApp().getConfiguration()->getNetworkConfiguration()->downloadUnknownLevels())
					gameController->changeState(new LevelDownloadingGameStateController(clientStartingHandler));
				else
				{
					// Reject the new level request and show an error message to the user
					data = new NetworkEventData();
					data->setDataType(NetworkEventData::NEW_LEVEL_REJECT);
					// No error handling since we are going to close the connection in any way
					data->write(socket);
					socket->Destroy();
					socket = NULL;
					wxGetApp().setClientSocket(NULL);
					wxGetApp().getMainFrame()->showMessageDialog(wxT("Could not change to new level, because it is unknown, and download is disabled!"), MainFrame::CLIENT_ERROR);
					gameController->changeState(new IdleGameStateController());
				}
			}
			break;
		case NetworkEventData::MESSAGING:
			// Read the data from the queue of the socket
			socket->Read(buffer, size);
			buffer = new char[data->getDataLength() + 1];
			memset(buffer, 0, data->getDataLength() + 1);
			socket->Read(buffer, data->getDataLength());
			// Check if an error occurred or not
			if(WxUtils::NONE != WxUtils::getSocketError(socket, data->getDataLength()))
			{
				// Failed to read the message...inform the user about it...
				data->setStringData(wxT("Failed to read message from remote side!!!"));
				color = WxUtils::RED;
			}
			else
				data->setRawData(buffer);
			message += data->getStringData();
			// Show the message only if the user enabled it
			if(true == wxGetApp().getMainFrame()->isMessagingEnabled())
			{
				if(false == wxGetApp().getMainFrame()->isMessagingFrameOpened())
					wxGetApp().getMainFrame()->createMessagingFrame();
				wxGetApp().getMainFrame()->getMessagingFrame()->getMessagingPanel()->addMessage(message, color);
			}
			else
			{
				// We need to inform the remote side that the messaging is disabled
				data = new NetworkEventData();
				data->setDataType(NetworkEventData::MESSAGING);
				data->setStringData(wxT("The messaging is currently disabled on my side"));
				socket->Write(data, size);
				socket->Write(data->getRawData(), data->getDataLength());
				// Inform the user as well, but only in the status bar
				wxGetApp().getMainFrame()->getStatusBar()->SetStatusText(wxGetApp().getMainFrame()->getStatusBar()->GetStatusText() + wxT(" - Opponent tried to send a message"));
			}
			break;
		default:
			// If we could not handle the input packet, let it to the substate
			mSubStateController->handleSocketInput(socketEvent);
			break;
	}
}

void GameStartedGameStateController::handleSocketOutput(wxSocketEvent&)
{
	wxASSERT_MSG(false, wxT("GameStartedGameStateController::handleSocketOutput after the game is started. Could not happen!"));
}

//
// Own methods
//

void GameStartedGameStateController::changeSubState(GameStartedSubStateController* subStateController)
{
	subStateController->addGameStartedController(this);
	WxUtils::TaskScheduler::schedule(new ChangeSubStateTask(this, subStateController));
}
